`include "common.svh"
`define CRAT_DIFFTEST
`ifdef CRAT_DIFFTEST
import "DPI-C" function int crat_create(
  int id,
  int gc_num,
  int prf_size,
  string name
);
import "DPI-C" function int crat_read(
  int id,
  int arf_idx,
  int prf_idx
);
import "DPI-C" function int crat_write(
  int id,
  int arf_idx,
  int prf_idx
);
import "DPI-C" function int crat_ctrl(
  int id,
  bit save_gc,
  bit commit_gc,
  bit restore_gc
);
`endif

module crat_wrapper #(
    parameter RENAME_WIDTH = 4,
    parameter COMMIT_WIDTH = 2,
    parameter GC_NUM = 256
) (
    input clk,
    input rst,

    input  ARF_IDX ars1[RENAME_WIDTH-1:0],
    input  ARF_IDX ars2[RENAME_WIDTH-1:0],
    input  ARF_IDX ard [RENAME_WIDTH-1:0],
    output PRF_IDX prs1[RENAME_WIDTH-1:0],
    output PRF_IDX prs2[RENAME_WIDTH-1:0],
    output PRF_IDX prd [RENAME_WIDTH-1:0],

    input rd_valid[RENAME_WIDTH-1:0],
    input PRF_IDX allocate_prd[RENAME_WIDTH-1:0],
    input save_gc[RENAME_WIDTH-1:0],
    output gc_full,
    input commit_gc[COMMIT_WIDTH-1:0],
    input restore_gc[COMMIT_WIDTH-1:0]

);
`ifdef CRAT_DIFFTEST
  logic [COMMIT_WIDTH-1:0] commit_gc_pack, restore_gc_pack;

  pack #(1, COMMIT_WIDTH) pack_commit_gc (
      .din (commit_gc),
      .dout(commit_gc_pack)
  );
  pack #(1, COMMIT_WIDTH) pack_restore_gc (
      .din (restore_gc),
      .dout(restore_gc_pack)
  );
  int int_ars1[RENAME_WIDTH-1:0];
  int int_ars2[RENAME_WIDTH-1:0];
  int int_ard[RENAME_WIDTH-1:0];
  int int_prs1[RENAME_WIDTH-1:0];
  int int_prs2[RENAME_WIDTH-1:0];
  int int_prd[RENAME_WIDTH-1:0];
  int int_allocate_prd[RENAME_WIDTH-1:0];
  always_comb begin
    integer i;
    for (i = 0; i < RENAME_WIDTH; i = i + 1) begin
      int_ars1[i] = 'b0;
      int_ars2[i] = 'b0;
      int_ard[i] = 'b0;
      int_prs1[i] = 'b0;
      int_prs2[i] = 'b0;
      int_prd[i] = 'b0;
      int_allocate_prd[i] = 'b0;
      int_ars1[i][`ARF_AW-1:0] = ars1[i];
      int_ars2[i][`ARF_AW-1:0] = ars2[i];
      int_ard[i][`ARF_AW-1:0] = ard[i];
      int_prs1[i][`PRF_AW-1:0] = prs1[i];
      int_prs2[i][`PRF_AW-1:0] = prs2[i];
      int_prd[i][`PRF_AW-1:0] = prd[i];
      int_allocate_prd[i][`PRF_AW-1:0] = allocate_prd[i];
    end
  end
  int id = 0;
  always_ff @(posedge clk) begin
    integer i;
    if (rst) begin
      id <= crat_create(id, GC_NUM, `PRF_SIZE, $sformatf("%m"));
    end else begin
      if (|(commit_gc_pack & restore_gc_pack)) begin
        for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
          id <= crat_ctrl(id, 0, commit_gc[i], restore_gc[i]);
        end
      end else begin
        for (i = 0; i < COMMIT_WIDTH; i = i + 1) begin
          if (commit_gc[i]) begin
            id <= crat_ctrl(id, 0, commit_gc[i], 0);
          end
        end
        for (i = 0; i < RENAME_WIDTH; i = i + 1) begin
          id <= crat_read(id, int_ars1[i], int_prs1[i]);
          id <= crat_read(id, int_ars2[i], int_prs2[i]);
          id <= crat_read(id, int_ard[i], int_prd[i]);
          if (rd_valid[i]) begin
            id <= crat_write(id, int_ard[i], int_allocate_prd[i]);
          end
          if (save_gc[i]) begin
            id <= crat_ctrl(id, save_gc[i], 0, 0);
          end
        end


      end

    end
  end
`endif

  PRF_IDX wdata_old[RENAME_WIDTH-1:0];
  ARF_IDX raddr[RENAME_WIDTH-1:0][2:0];
  PRF_IDX rdata[RENAME_WIDTH-1:0][2:0];
  logic we[RENAME_WIDTH-1:0];
  ARF_IDX waddr[RENAME_WIDTH-1:0];
  PRF_IDX wdata[RENAME_WIDTH-1:0];
  crat #(RENAME_WIDTH, COMMIT_WIDTH, GC_NUM) inst_crat (.*);
  genvar gi;
  generate
    //Bypass Rename Result Logic
    assign prs1[0] = rdata[0][0];
    assign prs2[0] = rdata[0][1];
    assign prd[0]  = rdata[0][2];
    // for (gi = 1; gi < RENAME_WIDTH; gi = gi + 1) begin : RenameBypass

    logic rs1_bypass_valid[RENAME_WIDTH-1:0]  /*verilator public_flat*/;
    logic rs2_bypass_valid[RENAME_WIDTH-1:0]  /*verilator public_flat*/;
    logic rd_bypass_valid [RENAME_WIDTH-1:0]  /*verilator public_flat*/;
    always_comb begin
      integer i, j;
      for (j = 0; j < RENAME_WIDTH; j = j + 1) begin

        rs1_bypass_valid[j] = 'b0;
        rs2_bypass_valid[j] = 'b0;
        rd_bypass_valid[j] = 'b0;
        prs1[j] = rdata[j][0];
        prs2[j] = rdata[j][1];
        prd[j] = rdata[j][2];
        for (i = 0; i < j; i = i + 1) begin
          rs1_bypass_valid[j] = rs1_bypass_valid[j] | ((ars1[j] == ard[i]) & rd_valid[i]);
          rs2_bypass_valid[j] = rs2_bypass_valid[j] | ((ars2[j] == ard[i]) & rd_valid[i]);
          rd_bypass_valid[j]  = rd_bypass_valid[j] | ((ard[j] == ard[i]) & rd_valid[i]);
          if ((ars1[j] == ard[i]) & rd_valid[i]) prs1[j] = allocate_prd[i];
          if ((ars2[j] == ard[i]) & rd_valid[i]) prs2[j] = allocate_prd[i];
          if ((ard[j] == ard[i]) & rd_valid[i]) prd[j] = allocate_prd[i];
        end
      end
    end
    // end
    for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin : RenamePorts
      assign raddr[gi][0] = ars1[gi];
      assign raddr[gi][1] = ars2[gi];
      assign raddr[gi][2] = ard[gi];
      assign waddr[gi] = ard[gi];
      assign wdata[gi] = allocate_prd[gi];
      assign wdata_old[gi] = prd[gi];
      assign we[gi] = rd_valid[gi];
    end
  endgenerate

endmodule


module crat #(
    parameter RENAME_WIDTH = 4,
    parameter COMMIT_WIDTH = 2,
    parameter GC_NUM = 16,
    localparam GC_AW = $clog2(GC_NUM)
) (
    input clk,
    input rst,

    input  ARF_IDX raddr[RENAME_WIDTH-1:0][2:0],
    output PRF_IDX rdata[RENAME_WIDTH-1:0][2:0],

    input we[RENAME_WIDTH-1:0],
    input ARF_IDX waddr[RENAME_WIDTH-1:0],
    input PRF_IDX wdata[RENAME_WIDTH-1:0],
    input PRF_IDX wdata_old[RENAME_WIDTH-1:0],
    input save_gc[RENAME_WIDTH-1:0],
    output gc_full,

    input commit_gc [COMMIT_WIDTH-1:0],
    input restore_gc[COMMIT_WIDTH-1:0]


);

  ARF_IDX map_table[`PRF_SIZE-1:0];
  bit map_table_valid[`PRF_SIZE-1:0];

`ifdef DEBUG
  logic [`PRF_SIZE-1:0] map_table_valid_pack  /*verilator public_flat*/;
  ARF_IDX [`PRF_SIZE-1:0] map_table_pack  /*verilator public_flat*/;
  pack #(`ARF_AW, `PRF_SIZE) pack_map_table (
      .din (map_table),
      .dout(map_table_pack)
  );
  pack #(1, `PRF_SIZE) pack_map_table_valid (
      .din (map_table_valid),
      .dout(map_table_valid_pack)
  );
`endif

  function PRF_IDX find_prf;
    input ARF_IDX arf_idx;
    PRF_IDX prf_idx;
    integer i;
    begin
      prf_idx = 'b0;
      for (i = 0; i < `PRF_SIZE; i = i + 1) begin
        if (map_table_valid[i] & (map_table[i] == arf_idx)) begin
          prf_idx = prf_idx | i[`PRF_AW-1:0];
        end
      end
    end
    return prf_idx;
  endfunction


  PRF_IDX prf_read[RENAME_WIDTH-1:0][2:0];
  genvar gi;
  generate

    // Global Check Point
    /* verilator lint_off UNUSEDSIGNAL */
    logic flush;
    logic almost_full;
    logic empty;
    logic i_valid;
    logic [`PRF_SIZE-1:0] i_data[RENAME_WIDTH-1:0];
    logic i_data_valid[RENAME_WIDTH-1:0];
    logic i_ready;
    logic o_valid/*verilator public_flat*/;
    logic [`PRF_SIZE-1:0] o_data[COMMIT_WIDTH-1:0];
    logic o_data_valid[COMMIT_WIDTH-1:0];
    logic o_data_ready[COMMIT_WIDTH-1:0];
    logic o_ready;
    /* verilator lint_on UNUSEDSIGNAL */


    logic [COMMIT_WIDTH-1:0] commit_gc_pack, restore_gc_pack;
    logic [RENAME_WIDTH-1:0] save_gc_pack, we_pack;

    pack #(1, COMMIT_WIDTH) pack_commit_gc (
        .din (commit_gc),
        .dout(commit_gc_pack)
    );
    pack #(1, COMMIT_WIDTH) pack_restore_gc (
        .din (restore_gc),
        .dout(restore_gc_pack)
    );
    pack #(1, RENAME_WIDTH) pack_save_gc (
        .din (save_gc),
        .dout(save_gc_pack)
    );
    pack #(1, RENAME_WIDTH) pack_we (
        .din (we),
        .dout(we_pack)
    );
    logic [`PRF_SIZE-1:0] restore_gc_data;
    MUX_PRIO #(COMMIT_WIDTH, `PRF_SIZE, 1) mux_restore_gc (
        .sel (commit_gc_pack & restore_gc_pack),
        .din (o_data),
        .dout(restore_gc_data)
    );

    logic [`PRF_SIZE-1:0] next_gc[RENAME_WIDTH-1:0];

    wire resotre_table = |restore_gc_pack;
    wire write_table = |we_pack;
    for (gi = 0; gi < `PRF_SIZE; gi = gi + 1) begin : map_table_reg
      logic   we_sel;
      ARF_IDX waddr_sel;
      always_comb begin
        integer i;
        we_sel = 1'b0;
        waddr_sel = 'b0;
        for (i = 0; i < RENAME_WIDTH; i = i + 1) begin
          if (we[i] & (wdata[i] == gi)) begin
            we_sel = 1'b1;
            waddr_sel = waddr[i];
          end
        end
      end
      reg_lr #(1, gi < 32 ? 1'b1 : 1'b0) map_table_valid_r (
          .load(resotre_table | write_table),
          .din (resotre_table ? restore_gc_data[gi] : next_gc[RENAME_WIDTH-1][gi]),
          .dout(map_table_valid[gi]),
          .*
      );
      reg_lr #(`ARF_AW, gi[`ARF_AW-1:0]) map_table_r (
          .load(we_sel),
          .din (waddr_sel),
          .dout(map_table[gi]),
          .*
      );
    end

    for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin
      assign prf_read[gi][0] = find_prf(raddr[gi][0]);
      assign prf_read[gi][1] = find_prf(raddr[gi][1]);
      assign prf_read[gi][2] = find_prf(raddr[gi][2]);
    end

    // for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin : caculate_next_gc
    always_comb begin
      integer i, j;
      for (j = 0; j < RENAME_WIDTH; j = j + 1) begin
        if (j == 0) begin
          for (i = 0; i < `PRF_SIZE; i = i + 1) begin
            next_gc[j][i] = map_table_valid[i];
          end
        end else begin
          next_gc[j] = next_gc[j-1];
        end
        if (we[j]) begin
          next_gc[j][wdata_old[j]] = 1'b0;
          next_gc[j][wdata[j]] = 1'b1;
        end
      end
    end
    // end

    multi_rw_fifo #(`PRF_SIZE, GC_AW, COMMIT_WIDTH, RENAME_WIDTH) gc_buffer (.*);
    assign gc_full = ~i_ready;
    assign flush = resotre_table;
    assign i_valid = |save_gc_pack;
    assign i_data_valid = save_gc;
    assign o_ready = |commit_gc_pack;
    assign o_data_ready = commit_gc;

    for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin : gc_buffer_ports
      always_comb begin
        integer i;
        for (i = 0; i < `PRF_SIZE; i = i + 1) begin
          i_data[gi][i] = next_gc[gi][i];
        end
      end

    end

    for (gi = 0; gi < RENAME_WIDTH; gi = gi + 1) begin : CRAT_ReadPorts
      assign rdata[gi][0] = prf_read[gi][0];
      assign rdata[gi][1] = prf_read[gi][1];
      assign rdata[gi][2] = prf_read[gi][2];
    end


  endgenerate
endmodule

module rat #(
    parameter READ_PORTS  = 4,
    parameter WRITE_PORTS = 2
) (
    input clk,
    input rst,

    input  ARF_IDX raddr[READ_PORTS-1:0],
    output PRF_IDX rdata[READ_PORTS-1:0],

    input we[WRITE_PORTS-1:0],
    input ARF_IDX waddr[WRITE_PORTS-1:0],
    input PRF_IDX wdata[WRITE_PORTS-1:0]

);

  PRF_IDX [`ARF_AW-1:0] map_table;
  integer i;
  always_ff @(posedge clk) begin
    if (rst) begin
      for (i = 0; i < `ARF_SIZE; i = i + 1) begin
        // map_table[i] <= {i}[`PRF_AW-1:0];
      end
    end else begin
      for (i = 0; i < WRITE_PORTS; i = i + 1) begin
        if (we[i]) begin
          map_table[waddr[i]] <= wdata[i];
        end
      end
    end
  end

  genvar gi;
  generate
    for (gi = 0; gi < READ_PORTS; gi = gi + 1) begin
      assign rdata[gi] = map_table[raddr[gi]];
    end
  endgenerate
endmodule
